﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ASquared.ModelOptimization;
using Csu.Modsim.ModsimModel;
using Csu.Modsim.ModsimIO;

namespace iwpm.optimization
{
    public class GrandCouleeTDG : IScenarioSolvable
    {
        // Static variables
        public readonly static char Delim = '\t';
        public readonly static char[] Delims = new char[] { Delim };

        // Instance variables
        private string _name; 
        private string _oldxyfile, _newxyfile, _weightsfile, _datafile;
        private Model _model;
        private LinearModeler _envModel;
        private Bounds _thresholds;
        private OptiModel _optiModel;
        private ConditionalGradientSolver _solver;
        private double _envWeight, _hydroWeight, _tolerance, _scaleFactor;
        private bool _error = false;
        private int _maxIterations;
        private string _scenariosFile, _dir, _basename;
        private StreamWriter _sw = null;
        private bool _writeLog = true;
        private Stopwatch _sWatch = new Stopwatch();

        // Properties
        public string Name
        {
            get
            {
                return _name; 
            }
        }
        
        public bool ErrorOccurred
        {
            get
            {
                return _error;
            }
        }

        public bool WriteLogFile
        {
            get
            {
                return _writeLog;
            }
            set
            {
                _writeLog = value;
            }
        }

        public TimeSpan SolutionTime
        {
            get
            {
                return _sWatch.Elapsed;
            }
        }

        // Constructors
        public GrandCouleeTDG(string oldxyfile, string newxyfile,
            string weightsfile, string datafile,
            Bounds thresholds, double envWeight, double hydroWeight, double scaleFactor,
            double tolerance, int maxIterations, string scenariosFile)
        {
            // set instance variables 
            _oldxyfile = oldxyfile;
            _newxyfile = newxyfile;
            _name = Path.GetFileNameWithoutExtension(_newxyfile); 
            _weightsfile = weightsfile;
            _datafile = datafile;
            _thresholds = thresholds;
            _envWeight = envWeight;
            _hydroWeight = hydroWeight;
            _tolerance = tolerance;
            _maxIterations = maxIterations;
            _scenariosFile = scenariosFile;
            _scaleFactor = scaleFactor;
        }

        // Event handling
        private void OnMessage(string message)
        {
            Console.WriteLine(message);
        }

        private void OnError(string message)
        {
            _error = true;
            Console.WriteLine(message);
        }

        // Methods 
        public void Setup()
        {
            // Set up the model 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(OnError);
            XYFileReader.Read(_model, _oldxyfile);

            // Import new data
            if (_scenariosFile != "")
            {
                DataImporter importer = new DataImporter(_model, _scenariosFile);
                importer.Import();
            }

            // Write the model file 
            _dir = Path.GetDirectoryName(Path.GetFullPath(_newxyfile));
            if (!Directory.Exists(_dir))
                Directory.CreateDirectory(_dir);
            _basename = _dir + "\\" + Path.GetFileNameWithoutExtension(_newxyfile);
            XYFileWriter.Write(_model, _newxyfile, XYFileWriter.CompressionLevelTypes.Full, OutputVersionType.LatestVersion);

            // Read the new model file 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(OnError);
            XYFileReader.Read(_model, _newxyfile);

            // Build the environmental modeler 
            _envModel = new LinearModeler(_weightsfile, _datafile,
                new GrandCouleeTDGMapper(_model), "#");

            // build OptiModel 
            EnvVsHydroInputs inputs = new EnvVsHydroInputs(_envModel, _thresholds, _envWeight, _hydroWeight);
            _optiModel = new OptiModel(_model, ObjectiveFormulation.EnvVsHydro, inputs);
            _model.maxit = _maxIterations;

            // build solver
            _solver = new ConditionalGradientSolver(_optiModel);
            _solver.Converged += new EventHandler(_solver_Converged);
            _solver.Tolerance = _tolerance;
            _solver.MaxIterations = _maxIterations;
        }

        void _solver_Converged(object sender, EventArgs e)
        {
            if (!_writeLog)
                return;

            if (_sw == null)
            {
                try
                {
                    _sw = new StreamWriter(_basename + "_log.csv");
                    _solver.LogFile = _basename + "_log_CGM.txt";
                    _optiModel.Debug(_basename + "_debug.csv");
                    _sw.WriteLine("Timestep,Iteration,EnvModel inputs,EnvModel output symbol,EnvModel Eval,EnvWeight,HydroWeight,Tolerance,Total Optimum,");
                }
                catch
                {
                    _writeLog = false;
                    _sw = null;
                    Console.WriteLine("Log file " + _basename + "_log.csv could not be opened for writing..."); 
                    return; 
                }
            }
            _sw.WriteLine(_model.mInfo.CurrentModelTimeStepIndex + "," +
                _model.mInfo.Iteration + "," +
                _envModel.CurrSymX.ToString() + "," +
                _envModel.CurrSymY.ToString() + "," + 
                _envModel.CurrEval.ToString() + "," + 
                _envWeight.ToString() + "," + 
                _hydroWeight.ToString() + "," + 
                _solver.Tolerance.ToString() + "," + 
                _solver.TotalOptimum);
            _sw.Flush();
        }

        public void Solve()
        {
            try
            {
                Setup();

                _sWatch.Reset();
                _sWatch.Start(); 
                _solver.Solve();
                _sWatch.Stop(); 
            }
            catch (Exception ex)
            {
                _model.FireOnError(ex.ToString());
            }
        }

        public override string ToString()
        {
            return _oldxyfile + "\t"
                + _newxyfile + "\t"
                + _weightsfile + "\t"
                + _datafile + "\t"
                + _thresholds.ToString() + "\t"
                + _envWeight.ToString() + "\t"
                + _hydroWeight.ToString() + "\t"
                + _scaleFactor.ToString() + "\t"
                + _tolerance.ToString() + "\t"
                + _maxIterations + "\t"
                + _scenariosFile;
        }

        public static GrandCouleeTDG FromString(string s)
        {
            string[] parameters = s.Split(Delims, StringSplitOptions.None);
            if (parameters.Length < 10) 
                return null; 

            double envWeight, hydroWeight, scaleFactor, tolerance; 
            int maxIterations;
            if (double.TryParse(parameters[5], out envWeight)
                && double.TryParse(parameters[6], out hydroWeight)
                && double.TryParse(parameters[7], out scaleFactor)
                && double.TryParse(parameters[8], out tolerance)
                && int.TryParse(parameters[9], out maxIterations))
            {
                return new GrandCouleeTDG(parameters[0], parameters[1], parameters[2], parameters[3],
                    Bounds.FromString(parameters[4]),
                    envWeight, hydroWeight, scaleFactor, tolerance, maxIterations, 
                    parameters.Length >= 11 ? parameters[10] : "");
            }
            else
            {
                return null;
            }
        }
    }
}
